package com.caishi.lkx.user.api;

import cn.hutool.core.util.StrUtil;
import com.aliyun.dysmsapi20170525.models.AddSmsTemplateResponseBody;
import com.aliyun.dysmsapi20170525.models.QuerySmsTemplateListResponseBody;
import com.caishi.lkx.common.AclResultCode;
import com.caishi.lkx.common.BaseApi;
import com.caishi.lkx.common.SystemService;
import com.caishi.lkx.common.config.DiamondConfig;
import com.caishi.lkx.common.context.RequireContext;
import com.caishi.lkx.common.context.Role;
import com.caishi.lkx.common.role.Roles;
import com.caishi.lkx.common.sms.AliyunSmsModel;
import com.caishi.lkx.common.util.SmsUtilService;
import com.caishi.lkx.user.ienum.type.UserAccountType;
import com.caishi.lkx.user.ienum.type.UserType;
import com.caishi.lkx.user.model.GroupModel;
import com.caishi.lkx.user.model.UserModel;
import com.caishi.lkx.user.service.IGroupService;
import com.caishi.lkx.user.service.ILoginInfoService;
import com.caishi.lkx.user.service.IUserService;
import com.caishi.lkx.user.service.impl.LoginInfoServiceImpl;
import com.zzw.common.annotation.ResultIgnore;
import com.zzw.common.exception.BizException;
import com.zzw.common.exception.BizRuntimeException;
import com.zzw.common.exception.QPSFailException;
import com.zzw.common.qps.RateLimiterParams;
import com.zzw.common.qps.RejectStrategy;
import com.zzw.common.qps.spring.RedisRateLimiterBean;
import com.zzw.common.util.RotateImage;

import com.caishi.lkx.user.UserResultCode;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.IOException;

/**
 * @author by keray
 * date:2021/5/21 10:51 上午
 */
@Tag(name = "短信验证码相关接口")
@RestController
@RequestMapping("/user/cu/sms")
@Roles
public class SmsCodeApi extends BaseApi {

    @Resource
    private SmsUtilService smsUtilService;

    @Resource
    private DiamondConfig diamondConfig;

    @Resource
    private IUserService<UserModel> userService;

    @Resource
    private ILoginInfoService loginInfoService;

    @Resource
    protected RedisRateLimiterBean rateLimiter;

    @Resource
    private SystemService systemService;
    @Resource
    private IGroupService groupService;


    @Operation(summary = "获取手机验证码")
    @Parameters({
            @Parameter(name = "phone", description = "手机号", required = true),
            @Parameter(name = "imageCheck", description = "是否校验图片旋转", example = "false"),
            @Parameter(name = "angel", description = "图片旋转角度 当imageCheck=true时必传", example = "0"),
            @Parameter(name = "sign", description = "签名 当不用图片旋转校验时必传"),
            @Parameter(name = "timestamp", description = "签名时间戳"),
            @Parameter(name = "smsCodeType", description = "获取手机验证码类型"),
    })
    @GetMapping("/phone-code/{phone}")
    @RequireContext({Role.APP,Role.PC})
    public void loginPhoneCode(@PathVariable String phone,
                               String sign,
                               Long timestamp,
                               @RequestParam(defaultValue = "false") Boolean imageCheck,
                               SmsUtilService.SmsCodeType smsCodeType,
                               HttpServletRequest request
    ) throws BizException, QPSFailException, InterruptedException {
        codeBeforeProcess(phone, smsCodeType, sign, timestamp, imageCheck, request);
        GroupModel groupModel = groupService.getById(context.getGroupId());
        smsUtilService.sendCode(phone, smsCodeType, AliyunSmsModel.builder().signName(groupModel.getSmsSignName()).groupId(String.valueOf(context.getGroupId())).accessKeyId(groupModel.getSmsAccessKeyId()).accessKeySecret(groupModel.getSmsAccessKeySecret()).endpoint(groupModel.getSmsEndpoint()).build());
    }


    @Operation(summary = "获取图片验证码")
    @GetMapping(value = "/image-code", produces = MediaType.IMAGE_JPEG_VALUE)
    @ResultIgnore
    public BufferedImage imageCode(HttpServletRequest request, HttpServletResponse response) throws IOException {
        return RotateImage.generate(request, response, diamondConfig.getImageCodeUrls());
    }

    private final String checkRateNamespace = "rate:sms:check";

    @Operation(summary = "图片验证码校验")
    @Parameters(@Parameter(name = "angel", description = "旋转角度 0-360", required = true))
    @GetMapping("/image-code-check")
    public boolean imageCodeCheck(HttpServletRequest request, @RequestParam Integer angel) throws QPSFailException, InterruptedException {
        var localGroupId = userContext.currentLocalGroupId();
        if (localGroupId == null && !userContext.loginStatus()) {
            // 未登录并且无效机构时ip限制
            rateLimiter.acquire(new RateLimiterParams()
                    .setKey(userContext.currentIp())
                    .setNamespace(checkRateNamespace)
                    .setMaxRate(200)
                    .setMillisecond(100)
                    .setRejectStrategy(RejectStrategy.wait)
                    .setWaitTime(100000)
            );
        } else if (userContext.loginStatus()) {
            rateLimiter.acquire(new RateLimiterParams()
                    .setKey(userContext.currentUserId())
                    .setNamespace(checkRateNamespace)
                    .setMaxRate(2)
                    .setMillisecond(500)
                    .setRejectStrategy(RejectStrategy.wait)
                    .setWaitTime(1000)
            );
        } else if (localGroupId != null) {
            rateLimiter.acquire(new RateLimiterParams()
                    .setKey(localGroupId.toString())
                    .setNamespace(checkRateNamespace)
                    .setMaxRate(500)
                    .setMillisecond(100)
                    .setRecoveryCount(5)
                    .setRejectStrategy(RejectStrategy.wait)
                    .setWaitTime(1000)
            );
        }
        return RotateImage.imageCodeCheck(angel, request);
    }

    private final String rateNamespace = "rate:sms:";

    private void codeBeforeProcess(String phone, SmsUtilService.SmsCodeType smsCodeType, String sign, Long timestamp, Boolean imageCheck, HttpServletRequest request) throws BizException, QPSFailException, InterruptedException {
        var currentUserId = userContext.currentUserId();
        if (smsCodeType == SmsUtilService.SmsCodeType.register) {
            var user = userService.getUserByMobile(phone);
            if (user != null && UserType.intAnyHave(user.getUserType(), new UserType[]{UserType.member})) {
                throw new BizRuntimeException("手机号已被注册");
            }
        } else if (smsCodeType == SmsUtilService.SmsCodeType.resetPass || smsCodeType == SmsUtilService.SmsCodeType.forgetPass) {
            var userId = userService.getUserIdByMobile(phone);
            if (StrUtil.isBlank(userId)) {
                throw new BizRuntimeException("手机号未注册");
            }
            if (smsCodeType == SmsUtilService.SmsCodeType.resetPass && !userId.equals(currentUserId)) {
                throw new BizRuntimeException("手机号错误");
            }
        } else if (smsCodeType == SmsUtilService.SmsCodeType.bindPhone) {
            if (userContext.loginStatus() && StrUtil.isNotBlank(userService.getUserMobileById(currentUserId))) {
                throw new BizException(UserResultCode.userHaveBindMobile);
            }
            var oldUserId = userService.getUserIdByMobile(phone);
            if (StrUtil.isNotBlank(oldUserId)) {
                var oldUserLoginInfo = loginInfoService.getUserHaveLogin(oldUserId);
                int currentLoginInfo = 0;
                if (userContext.loginStatus()) {
                    currentLoginInfo = loginInfoService.getUserHaveLogin(currentUserId);
                }
                if (!userContext.loginStatus() && StrUtil.isNotBlank(oldUserId)) {
                    var loginType = request.getParameter("type");
                    if (StrUtil.isBlank(loginType)) throw new BizException("请选择登录方式");
                    var accountType = UserAccountType.valueOf(loginType);
                    currentLoginInfo = ((LoginInfoServiceImpl) loginInfoService).getUserHaveLogin(accountType);
                }
                if ((oldUserLoginInfo & currentLoginInfo) != 0) {
                    throw new BizException(UserResultCode.mobileHaveBind);
                }
            }
        } else if (smsCodeType != SmsUtilService.SmsCodeType.login) {
            if (!userContext.loginStatus()) throw new BizException(AclResultCode.notLogin);
            if (smsCodeType == SmsUtilService.SmsCodeType.resetBindPhone && userService.mobileExits(phone, null)) {
                throw new BizRuntimeException(UserResultCode.mobileHaveBind);
            }
        }
        try {
            rateLimiter.acquire(new RateLimiterParams()
                    .setKey(phone)
                    .setNamespace(rateNamespace)
                    .setMaxRate(1)
                    .setMillisecond(3000)
            );
            rateLimiter.acquire(new RateLimiterParams()
                    .setKey(userContext.currentIp())
                    .setNamespace(rateNamespace)
                    .setMaxRate(200)
                    .setMillisecond(100)
                    .setRecoveryCount(2)
            );
            return;
        } catch (QPSFailException ignore) {
        }
        var aiCode = RotateImage.getAiCode(request);
        if (StrUtil.isNotEmpty(aiCode)) {
            // 限制aiCode认证的设备qps
            rateLimiter.acquire(new RateLimiterParams()
                    .setKey(aiCode)
                    .setNamespace(rateNamespace)
                    .setMaxRate(1)
                    .setMillisecond(3000)
                    .setRejectStrategy(RejectStrategy.wait)
                    .setWaitTime(15000)
            );
        }
        systemService.aiCheck(imageCheck, request, timestamp, sign, phone);
    }
    @RequireContext({Role.APP,Role.PC})
    @PostMapping("/phone-code/template")
    public AddSmsTemplateResponseBody createTemplate() throws Exception {
        GroupModel groupModel = groupService.getById(context.getGroupId());
        return smsUtilService.createTemplate(AliyunSmsModel.builder().signName(groupModel.getSmsSignName()).groupId(String.valueOf(context.getGroupId())).accessKeyId(groupModel.getSmsAccessKeyId()).accessKeySecret(groupModel.getSmsAccessKeySecret()).endpoint(groupModel.getSmsEndpoint()).build());
    }
    @RequireContext({Role.APP,Role.PC})
    @GetMapping("/phone-code/templates")
    public QuerySmsTemplateListResponseBody templates() throws Exception {
        GroupModel groupModel = groupService.getById(context.getGroupId());
        return smsUtilService.getTemplates(AliyunSmsModel.builder().signName(groupModel.getSmsSignName()).groupId(String.valueOf(context.getGroupId())).accessKeyId(groupModel.getSmsAccessKeyId()).accessKeySecret(groupModel.getSmsAccessKeySecret()).endpoint(groupModel.getSmsEndpoint()).build());
    }
}
